/*
 * alloc memory method is kmem.
 *
 * Copyright (C) 2014 Ingenic Semiconductor Co.,Ltd
 * Author: Aeolus <weiqing.yan@ingenic.com>
 */

#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>
#include <sys/fcntl.h>
#include <sys/mman.h>
#include <stdbool.h>

#include <icommon.h>
#ifdef DMALLOC
#include <imp/dmalloc.h>
#endif
#define MEM_DEV "/dev/rmem"
#define CMD_LINE "/proc/cmdline"

typedef struct {
	intptr_t vaddr;
	intptr_t paddr;
	uint32_t size;
} kmmc_info_t;

typedef struct {
	kmmc_info_t base;
	kmmc_info_t cur;
	bool b_init;
	int  kmem_fd;
	int index_cnt;
} kmmc_t;

static kmmc_t kmmc = {
	.base = {0, 0, 0},
	.cur = {0, 0, 0},
	.b_init = false,
	.kmem_fd = -1,
	.index_cnt = 0,
};

static int get_kmem_info(void)
{
	uint32_t addr = 0;
	int size = 0;
	char buf[512] = "";
	char *p = NULL;

	FILE *fb = fopen(CMD_LINE, "r");
	if(fb == NULL) {
		c_log(C_LOG_ERROR, "%s open file (%s) error\n", __func__, CMD_LINE);
		goto err_fopen_cmdline;
	}

	if (fread(buf, 1, sizeof(buf), fb) <= 0) {
		c_log(C_LOG_ERROR, "%s fread (%s) error\n", __func__, CMD_LINE);
		goto err_fread_cmdline;
	}

	if ((p = strstr(buf, "rmem")) == NULL) {
		c_log(C_LOG_ERROR, "%s fread (%s) error\n", __func__, CMD_LINE);
		goto err_strstr_rmem;
	}

	char *atsignpos = strchr(p, '@');
	if (*(atsignpos - 1) == 'M') {
		sscanf(p, "rmem=%dM@%x", &size, &addr);
		size = size * 1024 * 1024;
	} else if (*(atsignpos - 1) == 'K') {
		sscanf(p, "rmem=%dK@%x", &size, &addr);
		size = size * 1024;
	} else {
		goto err_rmem_unit;
	}

	kmmc.base.paddr = addr;
	kmmc.base.size = size;

	if (!kmmc.base.paddr || !kmmc.base.size) {
		c_log(C_LOG_ERROR, "CMD Line Rmem Size:%d, Addr:0x%08x is invalide\n",
				kmmc.base.size, kmmc.base.paddr);
		goto err_inv_kmem_addr_or_len;
	}

	c_log(C_LOG_INFO, "CMD Line Rmem Size:%x, Addr:0x%08x\n", kmmc.base.size, kmmc.base.paddr);

	fclose(fb);

	return 0;

err_inv_kmem_addr_or_len:
err_rmem_unit:
err_strstr_rmem:
err_fread_cmdline:
	fclose(fb);
err_fopen_cmdline:
	return -1;
}

int kmmc_init(void)
{
	if (get_kmem_info() < 0) {
		c_log(C_LOG_ERROR, "get kmem info failed\n");
		goto err_get_kmem_info;
	}

	kmmc.kmem_fd = open(MEM_DEV, O_RDWR);
	if(kmmc.kmem_fd <= 0) {
		c_log(C_LOG_ERROR, "open %s failed\n", MEM_DEV);
		goto err_open_mem_dev;
	}

	kmmc.base.vaddr = (intptr_t)mmap(NULL, kmmc.base.size, PROT_READ | PROT_WRITE, MAP_SHARED,
			kmmc.kmem_fd, kmmc.base.paddr);
	if(kmmc.base.vaddr <= 0) {
		c_log(C_LOG_ERROR, "mmap failed\n");
		goto err_mmap_kmem;
	}

	c_log(C_LOG_INFO, "kmem_vaddr=%x, kmem_paddr=%x, kmem_length=%x\n",
			kmmc.base.vaddr, kmmc.base.paddr, kmmc.base.size);
	memcpy(&kmmc.cur, &kmmc.base, sizeof(kmmc_info_t));
	kmmc.index_cnt = 0;
	kmmc.b_init = true;

	return 0;

err_mmap_kmem:
	close(kmmc.kmem_fd);
err_open_mem_dev:
err_get_kmem_info:
	return -1;
}

void kmmc_deinit(void)
{
	if ((kmmc.b_init == true) && (kmmc.index_cnt == 0) && (kmmc.kmem_fd > 0)) {
		if ((kmmc.base.vaddr > 0) && (kmmc.base.size > 0)) {
			munmap((void *)kmmc.base.vaddr, kmmc.base.size);
		}
		close(kmmc.kmem_fd);
		kmmc.kmem_fd = -1;
		kmmc.b_init = false;
		memset(&kmmc.cur, 0, sizeof(kmmc_info_t));
	}
}

void *c_kalloc(int index, int size, int align)
{
	intptr_t align_vaddr = 0, alloc_vaddr = 0;
	uint32_t align_size = 0;

	if (kmmc.b_init == false) {
		if (kmmc_init() < 0) {
			return NULL;
		}
	}

	align_vaddr = C_ALIGN(kmmc.cur.vaddr, align);
	align_size = kmmc.cur.size - (align_vaddr - kmmc.cur.vaddr);

	if (size < align_size) {
		alloc_vaddr = align_vaddr;
		kmmc.cur.vaddr = align_vaddr + size;
		kmmc.cur.size = align_size - size;
		kmmc.index_cnt++;
	}
	return (void *)alloc_vaddr;
}

void c_kfree(int index, void *addr)
{
	if (kmmc.index_cnt > 0) {
		kmmc.index_cnt--;
	}
	if (kmmc.index_cnt == 0) {
		kmmc_deinit();
	}
}

intptr_t c_kvirt_to_kphys(intptr_t vaddr)
{
	if((vaddr < kmmc.base.vaddr) || (vaddr > (kmmc.base.vaddr + kmmc.base.size))) {
		c_log(C_LOG_ERROR, "%s:vaddr input error:vaddr=%x, kmmc.base.vaddr=%x, kmmc.base.size=%x, kmmc.base.vaddr + kmmc.base.size=%x\n",
				__func__, vaddr, kmmc.base.vaddr, kmmc.base.size, kmmc.base.vaddr + kmmc.base.size);
		return 0;
	}

	return (kmmc.base.paddr + (vaddr - kmmc.base.vaddr));
}

intptr_t c_kphys_to_kvirt(intptr_t paddr)
{
	if((paddr < kmmc.base.paddr) || (paddr > (kmmc.base.paddr + kmmc.base.size))) {
		c_log(C_LOG_ERROR, "%s:paddr input error:paddr=%x, kmmc.base.paddr=%x, kmmc.base.size=%x, kmmc.base.paddr + kmmc.base.size=%x\n",
				__func__, paddr, kmmc.base.paddr, kmmc.base.size, kmmc.base.paddr + kmmc.base.size);
		return 0;
	}

	return (kmmc.base.vaddr + (paddr - kmmc.base.paddr));
}
